//-------------------------------------------------------------------------------
///
/// \author		Cem Yuksel (www.cemyuksel.com)
/// \date		November 19, 2012
///
/// \brief		Photon map visualization tool
/// \modified	Yuntao Ou (csyuntao.net)
/// \date		April 6, 2014
///
//-------------------------------------------------------------------------------

#include <stdlib.h>
#include <stdio.h>
//#include <math.h>
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
	#ifdef __WIN_32
	#include "GL/glut.h"
	#else
	#include <GL/glut.h>
	#endif
#endif
#define WINDOW_SIZE 800

#define max(a,b) (a>b?a:b)
/**
float max(float a, float b)
{
    return (a>b?a:b);
}
 **/

float viewAngle1=-60, viewAngle2=-120, viewTransZ=-200;
int mouseMode;
int mouseX, mouseY;

enum MouseModes {
	MOUSE_MODE_NONE,
	MOUSE_MODE_VIEW_ROTATE,
	MOUSE_MODE_VIEW_ZOOM,
};

//-------------------------------------------------------------------------------

/*
struct Photon {
  float pos[3];                 // photon position
  short plane;                  // splitting plane for kd-tree
  unsigned char theta, phi;     // incoming direction
  float power[3];               // photon power
};
*/

struct Photon{
    float flux[3];
    float pos[3];
    float dir[3];
};

int numPhotons = 0;
Photon *photons = NULL;

int numPhtVert = 500;

// z-curve test
void GetZcurveIndex(float * pos, int & o_index, int p=0)
{
    int fac = 1;
    for(int i=0; i<p; i++) fac *= 10;
    float facf = static_cast<float>(fac);
    
    long int ix = static_cast<long int>(pos[0] * facf);
    long int iy = static_cast<long int>(pos[1] * facf);
    long int iz = static_cast<long int>(pos[2] * facf);
    // TODO:
    long int ti = 0;
    for (int i=0; i<20; i++)
    {
        ti |= (ix & (1<<i)) << (i * 2); 	// x bin
        ti |= (iy & (1<<i)) << (i * 2 + 1); // y bin
        ti |= (iz & (1<<i)) << (i * 2 + 2); // z bin
    }
    o_index = ti;
}

//-------------------------------------------------------------------------------

void Display()
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

	glPushMatrix();

	// Set view
	glTranslatef( -12.5, -12.5, viewTransZ );
	glRotatef( viewAngle1, 1, 0, 0 );
	glRotatef( viewAngle2, 0, 0, 1 );

	// Draw photons
	glDrawArrays(GL_POINTS,0,numPhotons);

	glLineWidth(1.0f);
	//glDrawArrays(GL_LINE_STRIP, 0, numPhtVert);
	glDrawArrays(GL_LINE_STRIP, 0, numPhotons);

    // Draw xyz axis
	const float lineSize = 100;
	glBegin(GL_LINES);
	glColor3f(1,0,0);
	glVertex3f(0,0,0);
	glVertex3f(lineSize,0,0);
	glColor3f(0,1,0);
	glVertex3f(0,0,0);
	glVertex3f(0,lineSize,0);
	glColor3f(0,0,1);
	glVertex3f(0,0,0);
	glVertex3f(0,0,lineSize);
	glEnd();
	// */

	glPopMatrix();

	glutSwapBuffers();
}

//-------------------------------------------------------------------------------

void Reshape(int w, int h)
{
	glViewport( 0, 0, w, h );
	glMatrixMode( GL_PROJECTION );
	glLoadIdentity();
	float r = (float) w / float (h);
	gluPerspective( 60, r, 0.02, 10000.0);
	glMatrixMode( GL_MODELVIEW );
	glLoadIdentity();
}

//-------------------------------------------------------------------------------

void Mouse(int button, int state, int x, int y)
{
	mouseX = x;
	mouseY = y;
	if ( state == GLUT_DOWN ) {
		switch( button ) {
		case GLUT_LEFT_BUTTON:
			mouseMode = MOUSE_MODE_VIEW_ROTATE;
			break;
		case GLUT_RIGHT_BUTTON:
			mouseMode = MOUSE_MODE_VIEW_ZOOM;
			break;
		}
	} else {
		mouseMode = MOUSE_MODE_NONE;
	}
	glutPostRedisplay();
}

//-------------------------------------------------------------------------------

#define VIEW_ROTATE_INC 0.2f
#define VIEW_ZOOM_INC 0.5f

void MouseMove(int x, int y)
{

	switch ( mouseMode ) {
	case MOUSE_MODE_VIEW_ROTATE:
		viewAngle1 -= VIEW_ROTATE_INC * ( mouseY - y );
		viewAngle2 -= VIEW_ROTATE_INC * ( mouseX - x );
		break;
	case MOUSE_MODE_VIEW_ZOOM:
		viewTransZ += VIEW_ZOOM_INC * ( mouseY - y );
		break;
	}

	mouseX = x;
	mouseY = y;


	glutPostRedisplay();
}

//-------------------------------------------------------------------------------

void Keyboard(unsigned char key, int x, int y)
{
	switch(key)
	{
		case 'p':
		{
		    static int m = 0;
		    printf("Photon[%i]\n", m);
            /**
            printf("flux: ");
		    for (int i=0; i<3; i++) {
		        printf("%.2f, ", photons[m].flux[i]);
		    }
            /**/
		    printf("\npos: ");
		    for (int i=0; i<3; i++) {
		        printf("%.2f, ", photons[m].pos[i]);
		    }
            int zId = 0;
            GetZcurveIndex(photons[m].pos, zId, 0);
            printf("zId: %i\n", zId);
            /**
		    printf("\ndir: ");
		    for (int i=0; i<3; i++) {
		        printf("%.2f, ", photons[m].dir[i]);
		    }
            /**/
			printf("\n");
			m++;
		}
		break;
	
		case '+': numPhtVert++; if(numPhtVert>numPhotons) numPhtVert = numPhotons; break;
		case '-': numPhtVert--; if(numPhtVert<1) numPhtVert = 1; break;
		break;
	}

	glutPostRedisplay();
}

int main(int argc, char **argv)
{
	glutInit(&argc,argv);

	char defaultFileName[] = "photonmap.dat";
	char *fname = defaultFileName;
	if ( argc > 1 ) {
		fname = argv[1];
	}


	/////////////////////////////////////////////////////////////////////////////////
	// Read the photon map
	/////////////////////////////////////////////////////////////////////////////////
	FILE *fp = fopen(fname,"rb");
	if ( fp == NULL ) {
		printf("ERROR: Cannot open file \"%s\".\n", fname);
		return -1;
	}
	int n = 0;
    /**
	Photon buffer;
	for ( ; !feof(fp); n++ ) {
		fread(&buffer,sizeof(Photon),1,fp);
	}
	n--;
    **/
    fread(&n, sizeof(int), 1, fp);

	if ( n <= 0 ) {
		printf("ERROR: No photons found.\n");
	} else {
		photons = new Photon[n];
		//rewind(fp);
		int np = fread(photons,sizeof(Photon),n,fp);
		numPhotons = np;
		printf("%d photons read.\n",np);
	}
	fclose(fp);
	if ( n <= 0 ) return -2;
    
    float gray = 5.0f;
    double sumColor3 = 0.0;

    {
        int m = 0;
        float maxfl = max(photons[m].flux[0], max(photons[m].flux[1], photons[m].flux[2]));
        for (int x=0; x<3; x++) {
            sumColor3 += static_cast<double>(photons[m].flux[x]);
        }
        for (m = 1; m<numPhotons; m++)
        {
            for (int x=0; x<3; x++) {
                sumColor3 += static_cast<double>(photons[m].flux[x]);
            }
            maxfl = max(maxfl, max(photons[m].flux[0], max(photons[m].flux[1], photons[m].flux[2])));
        }
        if(maxfl > 0.01f)
            gray = 1.0f /maxfl;
        else
            gray = 1.0f;
        
    }
    double colorScale = 1.0;
    double averageInten = sumColor3*colorScale / (3.0 * static_cast<double>(n));
    printf("========= average flux power is %f / %.2f ================\n", averageInten, colorScale);
    //gray *= 0.7f;
    /**/
    //gray = 1.0f / 2530.0f;
    for (int m = 0; m < numPhotons; m++)
    {
        photons[m].flux[0] *= gray;
        photons[m].flux[1] *= gray;
        photons[m].flux[2] *= gray;
    }
    //---*/
	/////////////////////////////////////////////////////////////////////////////////


	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
	if (glutGet(GLUT_SCREEN_WIDTH) > 0 && glutGet(GLUT_SCREEN_HEIGHT) > 0){
		glutInitWindowPosition( (glutGet(GLUT_SCREEN_WIDTH) - WINDOW_SIZE)/2, (glutGet(GLUT_SCREEN_HEIGHT) - WINDOW_SIZE)/2 );
	}
	else glutInitWindowPosition( 50, 50 );
	glutInitWindowSize(WINDOW_SIZE, WINDOW_SIZE);

	glutCreateWindow("Photon Map Viz");
	glutDisplayFunc(Display);
	glutReshapeFunc(Reshape);
	glutMouseFunc(Mouse);
	glutMotionFunc(MouseMove);

	glutKeyboardFunc(Keyboard);

	glClearColor(0,0,0,0);
	glPointSize(1.0);
	glEnable( GL_DEPTH_TEST );
	glDisable( GL_LIGHTING );

	glEnableClientState(GL_VERTEX_ARRAY);
	glVertexPointer(3, GL_FLOAT, sizeof(Photon), photons[0].pos);
	glEnableClientState(GL_COLOR_ARRAY);
	glColorPointer(3, GL_FLOAT, sizeof(Photon), photons[0].flux);
    
    {
        int m = 0;
        printf("Photon[%i]\nflux: ", m);
        for (int i=0; i<3; i++) {
            printf("%.2f, ", photons[m].flux[i] / gray);
        }
        printf("\npos: ");
        for (int i=0; i<3; i++) {
            printf("%.2f, ", photons[m].pos[i]);
        }
        printf("\ndir: ");
        for (int i=0; i<3; i++) {
            printf("%.2f, ", photons[m].dir[i]);
        }
    }
    
    printf("\n");

	glutMainLoop();

	delete [] photons;

	return 0;
}

//-------------------------------------------------------------------------------
